﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using ChanSharp;
using System.Text.RegularExpressions;

namespace ArchiveSearch
{
    class ThreadPostFinder
    {
        DebugConsole debug;
        PostAdder postAdder;
        WebClient wc;
        List<ArchivedThread> archivedThreads;
        FilterSet filterSet;

        public ThreadPostFinder(DebugConsole debug, PostAdder postAdder, List<ArchivedThread> archivedThreads, FilterSet filterSet)
        {
            this.debug = debug;
            this.postAdder = postAdder;
            this.archivedThreads = archivedThreads;
            this.filterSet = filterSet;

            wc = new WebClient();
            wc.Headers.Add("user-agent", "ChanSharp Library (contact cosinemlpg@gmail.com)");
        }

        //the main method
        public void Start()
        {
            WriteLine("Indeed! I'll take it from here.");
            WriteLine("Selecting threads...");
            SelectThreads();

            if (archivedThreads.Count == 0)
            {
                WriteLine("There were no results!");
                return;
            }

            SearchThreads();

            WriteLine("That's all of the threads. I hope you found what you were looking for!");
            WriteLine("Oh yeah, remember to make a UI component for post results viewing.");

        }

        //download threads and search them for filterset-matching posts
        private void SearchThreads()
        {
            for (int t = 0; t < archivedThreads.Count; t++)
            {
                ChanSharp.Thread thread = new Thread(archivedThreads[t].URL);

                Write("Downloading thread " + archivedThreads[t].URL + " ... ");

                try { thread.DownloadPosts(); }
                catch (WebException ex)
                {
                    WriteLine("Whoops, shit, the thread downloading didn't go through. Take a look: ");
                    WriteLine(ex.Message);
                }

                WriteLine("Done. Searching posts.");

                for (int p = 0; p < thread.Posts.Count; p++)
                {
                    Post thisPost = thread.Posts[p];
                    try { SearchPost(thisPost); }
                    catch (Exception ex)
                    {
                        Write("Couldn't search a post! ID: ");
                        try { WriteLine(thisPost.ID); }
                        catch (Exception)
                        {
                            WriteLine("Man, it's so fucked up I can't even get the ID.");
                        }

                    }

                }
            }
        }
        
        //search a post with this instance's filterset
        private void SearchPost(Post thisPost)
        {
            bool isTime = thisPost.IsWithinTime(filterSet.FilterBefore, filterSet.FilterAfter);
            bool isID = thisPost.IsWithinID(filterSet.FilterPostMin, filterSet.FilterPostMax);
            bool isMD5 = string.IsNullOrEmpty(filterSet.FilterMD5) || (thisPost.File != null && filterSet.FilterMD5.Contains(thisPost.File.MD5));
            bool isTripcode = string.IsNullOrEmpty(filterSet.FilterTripcode) || thisPost.Tripcode.Contains(filterSet.FilterTripcode);
            bool isMatch;

            if (filterSet.FilterRegex)
            {
                isMatch = SearchPostRegex(thisPost);
            }
            else
            {
                isMatch = SearchPostSimple(thisPost);
            }


            if (isTime && isID && isMD5 && isTripcode && isMatch)
            {
                AddPost(thisPost);
            }

        }

        //returns false if any populated filter fields don't match the post, true otherwise
        private bool SearchPostRegex(Post thisPost)
        {
            Regex name = new Regex(filterSet.FilterName);
            Regex email = new Regex(filterSet.FilterEmail);
            Regex subject = new Regex(filterSet.FilterSubject);
            Regex comment = new Regex(filterSet.FilterComment);
            Regex filename = new Regex(filterSet.FilterFilename);

            bool isName = true, isEmail = true, isSubject = true, isComment = true, isFilename = true;

            if (!string.IsNullOrEmpty(filterSet.FilterName) && !name.IsMatch(thisPost.Name))
                isName = false;

            if (!string.IsNullOrEmpty(filterSet.FilterEmail) && !email.IsMatch(thisPost.Email))
                isEmail = false;

            if (!string.IsNullOrEmpty(filterSet.FilterSubject) && !subject.IsMatch(thisPost.Subject))
                isSubject = false;

            if (!string.IsNullOrEmpty(filterSet.FilterComment) && !comment.IsMatch(thisPost.Comment))
                isComment = false;

            if (!string.IsNullOrEmpty(filterSet.FilterFilename))
            {
                if (thisPost.File == null && !filename.IsMatch(thisPost.File.OriginalFilename))
                {
                    isFilename = false;
                }
            }

            return (isName && isEmail && isSubject && isComment && isFilename);
        }

        //same as above, only string.Contains() instead of regex
        private bool SearchPostSimple(Post thisPost)
        {
            bool isName = true, isEmail = true, isSubject = true, isComment = true, isFilename = true;

            if (!string.IsNullOrEmpty(filterSet.FilterName) && !thisPost.Name.Contains(filterSet.FilterName))
                isName = false;

            if (!string.IsNullOrEmpty(filterSet.FilterEmail) && !thisPost.Email.Contains(filterSet.FilterEmail))
                isEmail = false;

            if (!string.IsNullOrEmpty(filterSet.FilterSubject) && !thisPost.Subject.Contains(filterSet.FilterSubject))
                isSubject = false;

            if (!string.IsNullOrEmpty(filterSet.FilterComment) && !thisPost.Comment.Contains(filterSet.FilterComment))
                isComment = false;

            if (!string.IsNullOrEmpty(filterSet.FilterFilename))
            {
                if (thisPost.File == null && !thisPost.OriginalFilename.Contains(filterSet.FilterFilename))
                {
                    isFilename = false;
                }
            }

            return (isName && isEmail && isSubject && isComment && isFilename);
        }

        //use delegate to add post to search results 
        private void AddPost(Post post)
        {
            postAdder(post);
        }

        //use the datetime and/or ID range and/or board filters to download as few threads as possible
        private void SelectThreads()
        {
            List<ArchivedThread> goodThreads = archivedThreads.ToList<ArchivedThread>();

            if (filterSet.FilterBoard == Board.Co)
            {
                goodThreads = (from t in goodThreads
                               where t.URL.Contains("co")
                               select t).ToList<ArchivedThread>();
                WriteLine("Filtering for /co/");
            }
            if (filterSet.FilterBoard == Board.Mlp)
            {
                goodThreads = (from t in goodThreads
                               where t.URL.Contains("mlp")
                               select t).ToList<ArchivedThread>();
                WriteLine("Filtering for /mlp/");
            }
            if (filterSet.FilterBoard == Board.Both)
            {
                WriteLine("No board filtering.");
            }

            #region after
            int indexStartD = 0;
            DateTime roundDown = new DateTime(filterSet.FilterAfter.Year, filterSet.FilterAfter.Month, filterSet.FilterAfter.Day);

            for (int i = 0; i < goodThreads.Count; i++)
            {
                indexStartD = i;
                if (goodThreads[i].Date >= roundDown)
                    break;
            }
            #endregion

            #region before
            int indexEndD = goodThreads.Count - 1;
            DateTime roundUp = new DateTime(filterSet.FilterBefore.Year, filterSet.FilterBefore.Month, filterSet.FilterBefore.Day);

            for (int i = goodThreads.Count - 1; i >= 0; i--)
            {
                indexEndD = i;
                if (goodThreads[i].Date <= roundUp)
                    break;
            }
            #endregion

            indexStartD = Math.Max(indexStartD - 1, 0);
            goodThreads = goodThreads.GetRange(indexStartD, indexEndD - indexStartD + 1);

            WriteLine("After filtering for threads in the date-time range, we get " + goodThreads.Count.ToString() + ".");

            #region numbers
            int indexStart = 0;
            for (int i = 0; i < goodThreads.Count; i++)
            {
                indexStart = i;
                if (goodThreads[i].OPID > filterSet.FilterPostMin)
                    break;
            }

            int indexEnd = goodThreads.Count - 1;
            for (int i = goodThreads.Count - 1; i >= 0; i--)
            {
                indexEnd = i;
                if (goodThreads[i].OPID < filterSet.FilterPostMax)
                    break;
            }
            #endregion

            goodThreads = goodThreads.GetRange(indexStart, indexEnd - indexStart + 1);

            WriteLine("After filtering for threads in the post number range, we get " + goodThreads.Count.ToString() + ".");

            archivedThreads = goodThreads;

            if (goodThreads.Count == 0)
                return;

            WriteLine("The last one being " + goodThreads.Last().URL);
            WriteLine("The first one being " + goodThreads.First().URL);
            WriteLine("That's all the filtering I can get using just the front page.\n\n");
        }

        //use delegate to write to form console textbox
        private void Write(string msg)
        {
            debug(msg);
        }

        //same as above except newline appended
        private void WriteLine(string msg)
        {
            Write(msg + "\r\n");
        }

        //cut any threads short, to protect calling invalid form invoker thingies
        internal void Abort()
        {
            //but there are none
        }
    }
}
